package com.trgis.yhmap.util.wmtsproxy.controller;

import com.trgis.yhmap.util.wmtsproxy.WmtsXMLReader;
import com.trgis.yhmap.util.wmtsproxy.exception.WmtsConfigException;
import com.trgis.yhmap.util.wmtsproxy.filter.AccessRuler;
import com.trgis.yhmap.util.wmtsproxy.service.WMTSService;
import com.trgis.yhmap.util.wmtsproxy.vo.LayersVO;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.HttpClients;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 2021年2月3日 重写代理WMTS服务
 * 1.主要代理第三方WMTS的获取瓦片服务，可配置服务地址、服务名称、瓦片层级等参数
 * 2.代理本地geoserver服务，根据图层名称获取图层服务信息
 * 3.读取瓦片配置文件信息，主要可用来判断请求是否超过瓦片范围，配置调用参数
 * @author 王风雨
 * 2021年12月15日 增加可代理Rest风格的瓦片地址，格式为WMTS Capabilities.xml 中 </br>
 * <ResourceURL format="image/png" resourceType="tile" template="http://localhost:8081/wmtsproxy/wmts/rest/test/{style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}?format=image/png"/>
 * 2022年8月2日 修复因request请求参数大小写引发框架内部可能无法定位到具体的controller方法的问题
 * 2023年7月6日  wangfy 增加地图四至范围信息 *
 */
@RestController
@RequestMapping(value = "/wmts", method = RequestMethod.GET)
public class WMTSController {
    private static final Logger log = LoggerFactory.getLogger(WMTSController.class);

    /**
     * 每像素多少经纬度*
     */
    private final static double meter_unit_1 = (0.025428516728689246/(2 * Math.PI * 6378137 / 360)) / 96;
    /**
     * 每像素多少米*
     */
    private final static double meter_unit_2 = 0.0254000508001016 / 96;

    final AccessRuler accessRuler;

    final WMTSService wmtsService;

    private final WmtsXMLReader reader = WmtsXMLReader.getReader();

    public WMTSController(AccessRuler accessRuler, WMTSService wmtsService) {
        this.accessRuler = accessRuler;
        this.wmtsService = wmtsService;
    }

    /**
     * 常规的WMTS服务瓦片调用及参数
     */
    @GetMapping(params = "request=GetTile", produces = {"image/png", "image/jpg", "image/jpeg", "text/xml", "application/octet-stream"})
    public ResponseEntity<byte[]> getTile(
                          HttpServletRequest request) throws URISyntaxException {
        for (Enumeration<String> parameterNames = request.getParameterNames(); parameterNames.hasMoreElements();){
            String key = parameterNames.nextElement();
            if ("layer".equalsIgnoreCase(key)) {
                return getTile2(request.getParameter(key), request);
            }
        }
        return null;
    }

    @GetMapping(params = {"REQUEST=GetTile", "SERVICE=WMTS"}, produces = {"image/png", "image/jpg", "image/jpeg", "text/xml", "application/octet-stream"})
    public ResponseEntity<byte[]> getTile2(@RequestParam(required = false) String layer,
                                          HttpServletRequest request) throws URISyntaxException {

        if (null == layer) {
            for (Enumeration<String> parameterNames = request.getParameterNames(); parameterNames.hasMoreElements();){
                String key = parameterNames.nextElement();
                if ("layer".equalsIgnoreCase(key)) {
                    layer = request.getParameter(key);
                    break;
                }
            }
        }

        try {
            return ResponseEntity.ok(wmtsService.getTile(layer, request));
        } catch (WmtsConfigException e) {
            switch (e.getMsg()){
                case notFoundLayer :
//                    log.info("配置文件中没有配置这个图层的对应信息:{}", layer);
                    break;
                case notFoundUrl :
//                    log.info("没有配置对应的wmts瓦片地址:{}", layer);
                    break;
                case notFoundMatrix:
//                    log.info("没有配置对应的matrixSet:{}",e.getInfoStr());
                    break;
                case tileOutOfRange:
//                    log.error(e.getInfoStr());
                    break;
                case remoteException:
                    break;
            }
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .contentType(MediaType.TEXT_XML)
                    .body(toUserInfo(e.getInfoStr()).getBytes());
        }
    }

    @GetMapping(produces = {"image/png", "image/jpg", "image/jpeg", "text/xml", "application/octet-stream"})
    public ResponseEntity<byte[]> getTile3(@RequestParam(required = false) String layer,
                                           HttpServletRequest request) throws URISyntaxException {

        boolean getTile = false;

        for (Enumeration<String> parameterNames = request.getParameterNames(); parameterNames.hasMoreElements();){
            String key = parameterNames.nextElement();
            switch (key.toLowerCase(Locale.ROOT)){
                case "layer":
                    layer = request.getParameter(key);
                    break;
                case "request":
                    if ("getTile".equalsIgnoreCase(request.getParameter(key))) {
                        getTile = true;
                    }
                    break;
                default:
                    break;
            }
        }
        if (getTile) {
            return getTile2(layer, request);
        } else {
            return getCapabilitiesFromUrl(layer, request);
        }
    }

    // rest风格获取瓦片 http://127.0.0.1:8080/geoserver/gwc/service/wmts/rest/lpemapanno/{style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}?format=image/png
    @GetMapping(value = "/rest/{layer}/{style}/{tileMatrixSet}/{tileMatrix}/{tileRow}/{tileCol}", produces = {"image/png", "image/jpg", "image/jpeg", "text/xml", "application/octet-stream"})
    public ResponseEntity<byte[]> getTileRest(@PathVariable String layer,
                                              @PathVariable(value = "") String style,
                                              @PathVariable String tileMatrixSet,
                                              @PathVariable String tileMatrix,
                                              @PathVariable int tileRow,
                                              @PathVariable int tileCol,
                                              @RequestParam(defaultValue = "image/png", required = false) String format){
        try {
            return ResponseEntity.ok(wmtsService.getTileRest(layer, style, tileMatrixSet, tileMatrix, tileRow, tileCol, format));
        } catch (WmtsConfigException e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .contentType(MediaType.TEXT_XML)
                    .body(toUserInfo(e.getInfoStr()).getBytes());
        }
    }

    // rest风格获取瓦片 http://127.0.0.1:8080/geoserver/gwc/service/wmts/rest/lpemapanno//{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}?format=image/png
    @GetMapping(value = "/rest/{layer}//{tileMatrixSet}/{tileMatrix}/{tileRow}/{tileCol}", produces = {"image/png", "image/jpg", "image/jpeg", "text/xml", "application/octet-stream"})
    public ResponseEntity<byte[]> getTileRest2(@PathVariable String layer,
                                              @PathVariable String tileMatrixSet,
                                              @PathVariable String tileMatrix,
                                              @PathVariable int tileRow,
                                              @PathVariable int tileCol,
                                              @RequestParam(defaultValue = "image/png", required = false) String format){
        try {
            return ResponseEntity.ok(wmtsService.getTileRest(layer, "", tileMatrixSet, tileMatrix, tileRow, tileCol, format));
        } catch (WmtsConfigException e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .contentType(MediaType.TEXT_XML)
                    .body(toUserInfo(e.getInfoStr()).getBytes());
        }
    }
    /**
     * 常规方式获取WMTS服务图层信息
     * @param layer 指定的图层
     */
    @Cacheable(cacheNames = "xml")
    @GetMapping(params = "request=GetCapabilities", produces = "text/xml;charset=UTF-8")
    public ResponseEntity<byte[]> getCapabilitiesFromUrl(@RequestParam(name = "LAYER") String layer, HttpServletRequest request){
        log.info("获取图层 : {} 的服务信息" , layer);
        if (Optional.ofNullable(reader).isPresent()){
            try {
                return ResponseEntity.ok(reader.getCapabilities(layer).getBytes(StandardCharsets.UTF_8));
            } catch (WmtsConfigException e) {
                switch (e.getMsg()){
                    case notFoundLayer:
                        return ResponseEntity.ok(toUserInfo("没有发现图层的相关配置，请联系管理员").getBytes());
                    case notFoundCapabilities:
                        String localAddr = request.getLocalAddr();
                        int localPort = request.getLocalPort();
                        String reqURL = request.getRequestURL().toString();
                        log.info("从地图服务引擎端获取XML： {},   {},   {}", reqURL, localAddr, localPort);
//                        reqURL = reqURL.replaceAll("//[^/]*/", "//"+localAddr + ":" + localPort+"/");
                        return ResponseEntity.ok(getCapabilitiesFromUrl(layer, reader.getServerUrl(layer), reqURL).getBytes(StandardCharsets.UTF_8));
                    default:
                        return ResponseEntity
                                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                                .body(toUserInfo("读取服务配置信息发生错误，请联系管理员").getBytes());
                }
            }
        }
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(toUserInfo("找不到相关服务配置信息").getBytes());
    }

    @GetMapping(path = "/{layer}", produces = {"image/png", "image/jpg", "image/jpeg", "text/xml", "application/octet-stream"})
    public ResponseEntity<byte[]> get(@PathVariable String layer, HttpServletRequest request) throws URISyntaxException {
        for (Enumeration<String> params = request.getParameterNames(); params.hasMoreElements(); ) {
            String req = params.nextElement();
            if ("request".equalsIgnoreCase(req)) {
                if ("GetTile".equalsIgnoreCase(request.getParameter(req))) {
                    return getTile2(layer, request);
                }
            }
        }
        return ResponseEntity.status(HttpStatus.OK)
                .contentType(new MediaType(MediaType.TEXT_XML, StandardCharsets.UTF_8))
                .body(getCapabilitiesFromUrl(layer, request).getBody());
    }

    /**
     * 更改wmtsCon.xml或filterCon.properties配置文件后，重新读取配置内容，清除缓存
     * @param who 当日几号，如20
     */
    @GetMapping(path = "/refresh")
    @CacheEvict(cacheNames = {"xml", "tile", "all"}, allEntries = true)
    public String refresh(@RequestParam int who){
        if (LocalDate.now().getDayOfMonth() == who){
            log.info("重新读取配置文件wmtsCon.xml");
            accessRuler.start();
            return "刷新配置并清除缓存完成";
        }
        return "今天几号？";
    }

    @GetMapping(path = "/all")
    @Cacheable(cacheNames = "all")
    public LayersVO getAllLayers(){
        return reader.getAllLayers();
    }

    /**
     * 获取指定图层服务的GetCapabilities信息
     * @param layer 图层名称
     * @param url 图层服务的真实地址
     * @param proxyUrl 本代理的地址
     * @return xml格式内容
     */
    private String getCapabilitiesFromUrl(String layer, String url, String proxyUrl){
        String realLayer = reader.gerRealLayer(layer);
        URI uri ;
        try {
            URIBuilder uriBuilder = new URIBuilder(url);
            uriBuilder.setParameter("REQUEST", "GetCapabilities")
                      .setParameter("VERSION", "1.1.1")
                      .setParameter("SERVER", "WMTS");
            uri = uriBuilder.build();
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return toUserInfo("获取服务信息失败，配置的服务网络地址出现问题");
        }

        Document doc = null;
        HttpGet httpGet = new HttpGet(uri);
        httpGet.setHeader("Accept", MediaType.TEXT_XML_VALUE);
        try (CloseableHttpResponse response = HttpClients.createDefault().execute(httpGet)){
            if (HttpStatus.OK.value() == response.getStatusLine().getStatusCode()) {
//                log.info(response.getEntity().getContentType().getValue());
                SAXReader saxReader = new SAXReader();
                doc = saxReader.read(response.getEntity().getContent());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return toUserInfo("读取信息发生错误，请联系管理员");
        }

        StringWriter stringWriter = new StringWriter();
        // 获取layer节点下的 <ows:Identifier>  和 <TileMatrixSetLink><TileMatrixSet>
        // 不是指定图层的删除
        // 1表示经纬度，2表示平面坐标系
        if (doc != null) {
            Element rootElement = doc.getRootElement();
            Element contents = rootElement.element("Contents");
            List<Element> lys = contents.elements("Layer");
            final List<Element> tileMatrixSetLinks = new ArrayList<>();
            List<Element> tileMatrixSets = contents.elements("TileMatrixSet");
            List<String> links = new ArrayList<>();
            for (Iterator<Element> iterator = lys.iterator(); iterator.hasNext(); ) {
                Element lay = iterator.next();
                if (realLayer.equals(lay.element("Identifier").getText())) {
                    lay.element("Identifier").setText(layer);
                    tileMatrixSetLinks.addAll(lay.elements("TileMatrixSetLink"));
                    links = tileMatrixSetLinks.stream().map(link ->
                            link.element("TileMatrixSet").getText()
                    ).collect(Collectors.toList());
                } else {
                    iterator.remove();
                }
            }
            for (Iterator<Element> iterator = tileMatrixSets.iterator(); iterator.hasNext(); ) {
                Element tileMatrixSet = iterator.next();
                if (!links.contains(tileMatrixSet.elementText("Identifier"))) {
                    iterator.remove();
                } else {
                    String epsg = tileMatrixSet.elementText("SupportedCRS").split("::")[1];
                    int fl = 0; // 1表示经纬度，2表示平面坐标系
                    switch (epsg) {
                        case "4490":
                            fl = 1;
                            break;
                        case "4326":
                            fl = 1;
                            break;
                        case "2437":
                            fl = 2;
                            break;
                        default:
                            break;
                    }
                    final Double meter_unit = fl == 1 ? meter_unit_1 : (2 == fl ? meter_unit_2 : 0.28E-3);

                    tileMatrixSet.elements("TileMatrix").forEach(matrix -> {
                        double scale = Double.parseDouble(matrix.elementText("ScaleDenominator"));
                        double resolution = scale * meter_unit;
                        matrix.addElement("Resolution").setText(resolution + "");
                        String[] topLeftCorner = matrix.elementText("TopLeftCorner").split(" ");
                        double top = Double.parseDouble(topLeftCorner[1]);
                        double left = Double.parseDouble(topLeftCorner[0]);
                        int width = Integer.parseInt(matrix.elementText("TileWidth"));
                        int height = Integer.parseInt(matrix.elementText("TileHeight"));
                        // 找对应的瓦片行列数范围，计算BBOX
                        tileMatrixSetLinks.stream()
                            .filter(lk -> lk.elementText("TileMatrixSet").equals(tileMatrixSet.elementText("Identifier")))
                            .findAny().ifPresent(setLink -> setLink.element("TileMatrixSetLimits").elements("TileMatrixLimits").stream().filter(limit ->
                                limit.elementText("TileMatrix").equals(matrix.elementText("Identifier")))
                                    .findAny().ifPresent(tileLimit ->{
                                        int tr1 = Integer.parseInt(tileLimit.elementText("MinTileRow"));
                                        int tr2 = Integer.parseInt(tileLimit.elementText("MaxTileRow"));
                                        int tc1 = Integer.parseInt(tileLimit.elementText("MinTileCol"));
                                        int tc2 = Integer.parseInt(tileLimit.elementText("MaxTileCol"));
                                        double minX = tc1 * width * resolution + left;
                                        double maxX = (tc2 + 1) * width * resolution + left;
                                        double minY = top - (tr2 + 1) * height * resolution;
                                        double maxY = top - tr1 * height * resolution;
                                        matrix.addElement("BBox").setText(minX + " " + minY + " " + maxX + " " + maxY);
                                    }));
                    });
                }
            }
            if (!lys.isEmpty()) {
                try {
                    stringWriter.write(reader.changeSettings(layer, doc, proxyUrl));
                } catch (IOException e) {
                    e.printStackTrace();
                    stringWriter.write(toUserInfo("获取图层配置信息时发生错误，请联系管理员"));
                }
            } else {
                stringWriter.write(toUserInfo("没有相关图层的信息，请联系管理员"));
            }
        }

        return stringWriter.toString();
    }

    private String toUserInfo(String msg){
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<info>" + msg + "</info>";
    }
}
